#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <unistd.h>
#include <cstring>
#include <pthread.h>
#include <functional>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "ThreadPool.hpp"

enum
{
  SOCKET_ERROR = 1,
  BIND_ERROR,
  LISTEN_ERROR,
  USAGE_ERROR
};

const static int sockfddefault = -1;
const static int gbacklog = 16;

class TcpServer; // 声明

class ThreadData
{
public:
  ThreadData(int fd, InetAddr addr, TcpServer *s) : sockfd(fd), clientaddr(addr), self(s)
  {
  }

public:
  int sockfd;
  InetAddr clientaddr;
  TcpServer *self;
};

using task_t = std::function<void()>;

class TcpServer
{
public:
  TcpServer(int port) : _port(port), _listensockfd(sockfddefault), _isrunning(false)
  {
  }
  void InitServer()
  {
    // 1. 创建流式套接字
    _listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
    if (_listensockfd < 0)
    {
      LOG(FATAL, "socket error");
      exit(SOCKET_ERROR);
    }
    LOG(DEBUG, "socket create success,sockfd is : %d", _listensockfd);

    // 2. 绑定
    struct sockaddr_in local;      // struct sockaddr_in 系统提供的数据类型。local是变量，用户栈上开辟空间。
    bzero(&local, sizeof(local));  // 将从&local开始的sizeof(local)大小的内存区域置零
    local.sin_family = AF_INET;    // 设置网络通信方式
    local.sin_port = htons(_port); // port要经过网络传输给对面，所有需要从主机序列转换为网络序列
    local.sin_addr.s_addr = INADDR_ANY;

    int n = bind(_listensockfd, (struct sockaddr *)&local, sizeof(local));
    if (n < 0)
    {
      LOG(FATAL, "bind error");
      exit(BIND_ERROR);
    }
    LOG(DEBUG, "bind success,sockfd is : %d", _listensockfd);

    // 3. tcp是面向连接的，所以通信之前，必须先建立连接，服务器是被链接的
    //  tcpserver启动，未来首先要一直等待客户端的连接，listen
    n = listen(_listensockfd, gbacklog);
    if (n < 0)
    {
      LOG(FATAL, "listen error");
      exit(LISTEN_ERROR);
    }
    LOG(DEBUG, "listen success,sockfd is : %d", _listensockfd);
  }
  void Service(int sockfd, InetAddr client)
  {
    LOG(DEBUG, "get a new link ,info %s:%d,fd:%d", client.Ip(), client.Port(), sockfd);
    std::string clientaddr = "[" + client.Ip() + ":" + std::to_string(client.Port()) + "]#";
    while (true)
    {
      // tcp连接面向字节流，可以使用文件接口：read，write
      char inbuffer[1024];
      ssize_t n = read(sockfd, inbuffer, sizeof(inbuffer) - 1);
      if (n > 0)
      {
        inbuffer[n] = 0;
        std::cout << clientaddr << inbuffer << std::endl;

        std::string echo_string = "[server echo]# ";
        echo_string += inbuffer;

        write(sockfd, echo_string.c_str(), echo_string.size());
      }
      else if (n == 0) // read返回值如果为0，表示读到了文件结尾，即client退出&&关闭连接了
      {
        LOG(INFO, "%s quit", clientaddr.c_str());
        break;
      }
      else
      {
        LOG(ERROR, "read error");
        break;
      }
    }
    close(sockfd); // 文件描述符泄露
  }
  static void *HandlerSock(void *args)
  {
    pthread_detach(pthread_self()); // 线程分离
    ThreadData *td = static_cast<ThreadData *>(args);
    // 需要调用Service函数，但是Service函数是类内函数，静态成员函数没有this指针无法调用，如何解决？
    // 将this指针设为ThreadData的类内成员，再通过这个this调用Service
    td->self->Service(td->sockfd, td->clientaddr);
    delete td;
    return nullptr;
  }
  void Loop()
  {
    _isrunning = true;
    // 4. 不能直接接收数据，先获取连接
    while (_isrunning)
    {
      struct sockaddr_in peer;
      socklen_t len = sizeof(peer);
      // accept会阻塞等待，直到有客户端连接
      int sockfd = ::accept(_listensockfd, (struct sockaddr *)&peer, &len);
      if (sockfd < 0)
      {
        LOG(WARNING, "accept error");
        continue; // 失败了就继续获取就行，不需要退出
      };
      // version 0 ：一次只能处理一个请求
      // Service(sockfd, InetAddr(peer));

      // version 1 ：采用多进程
      // 父子进程文件描述符表是独立的，拷贝
      // pid_t id = fork();
      // if (id == 0)
      // {
      //   // child ：关心sockfd，不关心listensockfd
      //   close(_listensockfd); // 建议关闭，防止子进程误写_listensockfd
      //   if (fork() > 0)
      //     exit(0);                       // 子进程直接退出，留下孙子进程（孤儿），被系统领养，执行完service自动回收
      //   Service(sockfd, InetAddr(peer)); // Service由孙子进程执行
      //   exit(0);
      // }
      // // father ：关心listensockfd，不关心sockfd，因为父进程已经将sockfd交给了子进程
      // close(sockfd);           // 必须关闭，防止父进程打开过多的文件描述符而不关闭
      // waitpid(id, nullptr, 0); // 子进程直接退出，所以这里直接瞬间等待成功，所以可以继续accept

      // version 2 ：采用多线程
      // pthread_t t;
      // ThreadData *td = new ThreadData(sockfd, InetAddr(peer), this);
      // pthread_create(&t, nullptr, HandlerSock, td);

      // version 3 ： 采用线程池
      task_t t = std::bind(&TcpServer::Service, this, sockfd, InetAddr(peer));
      ThreadPool<task_t>::GetInstance()->Enqueue(t);
    }
    _isrunning = false;
  }
  ~TcpServer()
  {
    if (_listensockfd > sockfddefault)
      close(_listensockfd);
  }

private:
  uint16_t _port;
  int _listensockfd;
  bool _isrunning;
};